#pragma once

#include "math_compare.h"
#include "Random.hpp"
#include <math/number.hpp>




float dot(const D3DXVECTOR2 &left, const D3DXVECTOR2 &right)
{
	return D3DXVec2Dot(&left, &right);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

float dot(const D3DXVECTOR3 &left, const D3DXVECTOR3 &right)
{
	return D3DXVec3Dot(&left, &right);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

float dot(const D3DXVECTOR4 &left, const D3DXVECTOR4 &right)
{
	return D3DXVec4Dot(&left, &right);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

D3DXVECTOR3 cross(const D3DXVECTOR3 &left, const D3DXVECTOR3 &right)
{
	D3DXVECTOR3 result;
	D3DXVec3Cross(&result, &left, &right);
	return result;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template
<
	class vector_t,
	class float_t
>
float_t length(const vector_t &vec)
{
	return vec.length();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template<>
float length(const D3DXVECTOR2 &vec)
{
	return D3DXVec2Length(&vec);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template<>
float length(const D3DXVECTOR3 &vec)
{
	return D3DXVec3Length(&vec);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template<>
float length(const D3DXVECTOR4 &vec)
{
	return D3DXVec4Length(&vec);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template
<
	class matrix_t
>
matrix_t identity(matrix_t mat)
{
	mat.identity();
	return mat;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template <>
D3DXMATRIX identity(D3DXMATRIX mat)
{
	D3DXMatrixIdentity(&mat);
	return mat;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template
<
	class matrix_t,
	class float_t
>
float_t determinant(matrix_t mat)
{
	return mat.determinant();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template <>
float determinant(D3DXMATRIX mat)
{
	return D3DXMatrixDeterminant(&mat);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template
<
	class matrix_t
>
matrix_t inverse(matrix_t mat)
{
	mat.inverse();
	return mat;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template <>
D3DXMATRIX inverse(D3DXMATRIX mat)
{
	D3DXMatrixInverse(&mat, NULL, &mat);
	return mat;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template
<
	typename matrix_t,
	typename vector_t
>
vector_t transform(const matrix_t& left, const vector_t& right)
{
	vector_t result;
	return right.transform(left);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template <>
D3DXVECTOR4 transform<D3DXMATRIX, D3DXVECTOR4>(const D3DXMATRIX &left, const D3DXVECTOR4 &right)
{
	D3DXVECTOR4 result;
	D3DXVec4Transform(&result, &right, &left);
	return result;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template <typename T>
T scale(float x, float y, float z);

template <>
float4x4 scale<float4x4>(float x, float y, float z)
{
	float4x4 result = float4x4::scale(x, y, z);
	return result;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template <>
D3DXMATRIX scale<D3DXMATRIX>(float x, float y, float z)
{
	D3DXMATRIX result;
	D3DXMatrixScaling(&result, x, y, z);
	return result;
}
///////////////////////////////////////////////////////////////////////////////////////////////////




template
<
	class expected_t,
	class actual_t
>
void add_test(RandomCache& cache)
{
	expected_t expected = cache.generate<expected_t>() + cache.generate<expected_t>();
	actual_t   actual   = cache.generate<actual_t>() + cache.generate<actual_t>();

	ASSERT_EQUAL(expected, actual);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template
<
	class expected_t,
	class actual_t
>
void sub_test(RandomCache& cache)
{
	expected_t expected = cache.generate<expected_t>() - cache.generate<expected_t>();
	actual_t   actual   = cache.generate<actual_t>() - cache.generate<actual_t>();

	ASSERT_EQUAL(expected, actual);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template
<
	class expected_t,
	class actual_t
>
void mul_test(RandomCache& cache)
{
	// Vector * Scalar
	{
		expected_t expected = cache.generate<expected_t>() * cache.generate<float>();
		actual_t   actual   = cache.generate<actual_t>() * cache.generate<float>();

		ASSERT_EQUAL(expected, actual);
	}

	// Scalar * Vector
	{
		expected_t expected = cache.generate<float>() * cache.generate<expected_t>();
		actual_t   actual   = cache.generate<float>() * cache.generate<actual_t>();

		ASSERT_EQUAL(expected, actual);
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template
<
	class expected_t,
	class actual_t
>
void div_test(RandomCache& cache)
{
	// Vector / Scalar
	{
		expected_t expected = cache.generate<expected_t>() / cache.generate<float>();
		actual_t   actual   = cache.generate<actual_t>() / cache.generate<float>();

		ASSERT_EQUAL(expected, actual);
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template
<
	class expected_t,
	class actual_t
>
void dot_test(RandomCache& cache)
{
	float expected = dot(cache.generate<expected_t>(), cache.generate<expected_t>());
	float actual   = dot(cache.generate<actual_t>(), cache.generate<actual_t>());

	ASSERT_EQUAL(expected, actual);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template
<
	class expected_t,
	class actual_t
>
void cross_test(RandomCache& cache)
{
	expected_t expected = cross(cache.generate<expected_t>(), cache.generate<expected_t>());
	actual_t   actual   = cross(cache.generate<actual_t>(), cache.generate<actual_t>());

	ASSERT_EQUAL(expected, actual);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template
<
	class expected_t,
	class actual_t
>
void length_test(RandomCache& cache)
{
	float expected = length<expected_t,float>(cache.generate<expected_t>());
	float actual   = length<actual_t,float>(cache.generate<actual_t>());

	ASSERT_EQUAL(expected, actual);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template
<
	typename expected_t,
	typename actual_t
>
void serialize_test(RandomCache& cache)
{
	expected_t expected = cache.generate<expected_t>();
	std::stringstream stream;
	stream << expected;

	actual_t actual = boost::lexical_cast<actual_t>(stream.str().c_str());

	ASSERT_EQUAL(expected, actual);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template
<
	class expected_t,
	class actual_t
>
void identity_test(RandomCache& cache)
{
	expected_t expected = identity(cache.generate<expected_t>());
	actual_t   actual   = identity(cache.generate<actual_t>());

	ASSERT_EQUAL(expected, actual);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template
<
	class expected_t,
	class actual_t
>
void determinant_test(RandomCache& cache)
{
	float expected = determinant<expected_t,float>(cache.generate<expected_t>());
	float actual   = determinant<actual_t,float>(cache.generate<actual_t>());

	ASSERT_EQUAL_DELTA(expected, actual, math::epsilon<float>()());
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template
<
	class expected_t,
	class actual_t
>
void inverse_test(RandomCache& cache)
{
	expected_t expected = inverse(cache.generate<expected_t>());
	actual_t   actual   = inverse(cache.generate<actual_t>());

	ASSERT_EQUAL(expected, actual);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

template
<
	class expected_matrix_t,
	class expected_vector_t,
	class actual_matrix_t,
	class actual_vector_t
>
void transform_test(RandomCache& cache)
{
	// transform the vector with the matrix
	expected_vector_t expected = transform(cache.generate<expected_matrix_t>(), cache.generate<expected_vector_t>());
	actual_vector_t   actual   = transform(cache.generate<actual_matrix_t>(), cache.generate<actual_vector_t>());

	ASSERT_EQUAL(expected, actual);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




template
<
	class expected_t,
	class actual_t
>
void scale_test(RandomCache& cache)
{
	// transform the vector with the matrix
	expected_t expected = scale<expected_t>(cache.generate<float>(), cache.generate<float>(), cache.generate<float>());
	actual_t   actual   = scale<actual_t>(cache.generate<float>(), cache.generate<float>(), cache.generate<float>());

	ASSERT_EQUAL(expected, actual);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
